'use strict'

/**
 * Torrent 资源片
 * 增加 peepNextReseve, finished 方法
 *  参考: https: //www.npmjs.com/package/torrent-piece
 */
const BLOCK_LENGTH = 1 << 14;     // 16k

class Piece {
  constructor(length) {
    this.length = length
    this.missing = length
    this.sources = null

    this._chunks = Math.ceil(length / BLOCK_LENGTH)
    this._remainder = (length % BLOCK_LENGTH) || BLOCK_LENGTH
    this._buffered = 0
    this._buffer = null
    this._cancellations = null
    this._reservations = 0
    this._flushed = false
  }

  chunkLength(i) {
    return i === this._chunks - 1 ? this._remainder : BLOCK_LENGTH
  }

  chunkOffset(i) {
    return i * BLOCK_LENGTH
  }

  reserve() {
    if (!this.init()) return -1
    if (this._cancellations.length) return this._cancellations.pop()
    if (this._reservations < this._chunks) return this._reservations++
    return -1
  }

  peepNextReseve() {
    if (!this.init()) return -1;
    if (this._cancellations.length) return this._cancellations[this._cancellations.length - 1];
    if (this._reservations < this._chunks) return this._reservation;
    return -1
  }

  cancel(i) {
    if (!this.init()) return
    this._cancellations.push(i)
  }

  get(i) {
    if (!this.init()) return null
    return this._buffer[i]
  }

  set(i, data, source) {
    if (!this.init()) return false
    const len = data.length
    const blocks = Math.ceil(len / BLOCK_LENGTH)
    for (let j = 0; j < blocks; j++) {
      if (!this._buffer[i + j]) {
        const offset = j * BLOCK_LENGTH
        const splitData = data.slice(offset, offset + BLOCK_LENGTH)
        this._buffered++
        this._buffer[i + j] = splitData
        this.missing -= splitData.length
        if (!this.sources.includes(source)) {
          this.sources.push(source)
        }
      }
    }
    return this._buffered === this._chunks
  }

  finished() {
    return this._buffered === this._chunks;
  }

  flush() {
    if (!this._buffer || this._chunks !== this._buffered) return null
    const buffer = Buffer.concat(this._buffer, this.length)
    this._buffer = null
    this._cancellations = null
    this.sources = null
    this._flushed = true
    return buffer
  }

  init() {
    if (this._flushed) return false
    if (this._buffer) return true
    this._buffer = new Array(this._chunks)
    this._cancellations = []
    this.sources = []
    return true
  }
}

Object.defineProperty(Piece, 'BLOCK_LENGTH', {
  value: BLOCK_LENGTH
})

module.exports.Piece = Piece;
